/**
 * Megasquirt Logger for PalmOS
 * Copyright (C) 2011 Andrew Beard
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <PalmOS.h>
#include <PalmOSGlue.h>
#include <DataMgr.h>
#include <StdIOPalm.h>
#include <VFSMgr.h>

/* MathLib headers */
#include "MathLib.h"

#include "MegasquirtLogger.h"
#include "MegasquirtLogger_Rsc.h"
#include "MegasquirtInterface.h"
#include "LogDatabase.h"
#include "ExportDatabase.h"
#include "LogManager.h"
#include "SelectedLogDatabase.h"

/*********************************************************************
 * Entry Points
 *********************************************************************/

/*********************************************************************
 * Global variables
 *********************************************************************/
/*
 * g_prefs
 * cache for application preferences during program execution
 */
MegasquirtLoggerPreferenceType g_prefs;
MegasquirtInterface*           g_pMegasquirtInterface = NULL;
LogDatabase*                   g_pLogDatabase         = NULL;
LogManager*                    g_pLogManager          = NULL;
SelectedLogDatabase*           g_pSelectedLogDatabase = NULL;
UInt16                         g_nSerialPortID        = 0;
UInt32                         g_nNumUpdateRecords    = 0;
UInt16                         g_nPreviousAutoOffTime = 0;

/*********************************************************************
 * Internal Constants
 *********************************************************************/

/* Define the minimum OS version we support */
#define ourMinVersion    sysMakeROMVersion(4,0,0,sysROMStageDevelopment,0)
#define kPalmOS20Version sysMakeROMVersion(2,0,0,sysROMStageDevelopment,0)

/*********************************************************************
 * Internal Functions
 *********************************************************************/

/**
 * Checks if the device has VFS support.
 *
 * @return true if the device has VFS Manager support, false otherwise.
 */
static bool VFS_ManagerPresent( void )
{
    Err    error;
    UInt32 nVfsMgrVersion = 0;

    error = FtrGet( sysFileCVFSMgr,
                    vfsFtrIDVersion,
                    &nVfsMgrVersion );
    if ( error )
    {
        // VFS Manager not installed
        return false;
    }
    else
    {
        // VFS Manager Installed - vfsMgrVersion contains version number
        return true;
    }
}

/**
 * Fills in the version number on the main form from the resources.
 */
static void PopulateMainFormVersion( void )
{
    MemHandle  hOldTextHandle;
    MemHandle  hNewTextHandle;
    MemHandle  hVersionStringHandle;
    FormPtr    currentForm;
    Char*      pVersionString;
    Char*      pFieldString;

    FieldType* field;
    UInt16     fieldIndex;

    currentForm = FrmGetActiveForm();

    // Allocate a handle for our new field text
    hNewTextHandle = MemHandleNew( 20 );
    pFieldString = (Char*)MemHandleLock( hNewTextHandle );

    // Lock the version string resource
    hVersionStringHandle = DmGetResource( 'tSTR',
                                          AppVersion );

    // Build the string that goes on the form
    StrCopy( pFieldString,
             "Version " );
    pVersionString = (Char*)MemHandleLock( hVersionStringHandle );
    StrCat( pFieldString,
            pVersionString );

    MemHandleUnlock( hNewTextHandle );

    // Let go of the version string resource
    MemHandleUnlock( hVersionStringHandle );
    DmReleaseResource( hVersionStringHandle );

    // Deal with the Version field
    fieldIndex = FrmGetObjectIndex( currentForm,
                                    MainFieldVersion );
    field = (FieldType*)FrmGetObjectPtr( currentForm,
                                         fieldIndex );
    hOldTextHandle = FldGetTextHandle( field );

    // Set the new text
    FldSetTextHandle( field,
                      hNewTextHandle );
    FldDrawField( field );


    if ( hOldTextHandle )
    {
        MemHandleFree( hOldTextHandle );
    }
}

/**
 * Updates a field on the current form with the given string.
 *
 * @param[in] pString String to show in the field.
 * @param[in] nField The field ID to populate.
 */
static void PopulateFieldWithString( const Char* pString,
                                     UInt16      nField )
{
    MemHandle   hOldTextHandle;
    MemHandle   hNewTextHandle;
    FormPtr     currentForm;
    Char*       pFieldString;

    FieldType* field;
    UInt16     fieldIndex;

    currentForm = FrmGetActiveForm();

    // Allocate a handle for our new field text
    hNewTextHandle = MemHandleNew( StrLen(pString) + 1 );
    pFieldString = (Char*)MemHandleLock( hNewTextHandle );

    // Copy the string that goes on the form
    StrCopy( pFieldString,
             pString );
    MemHandleUnlock( hNewTextHandle );

    // Deal with the Version field
    fieldIndex = FrmGetObjectIndex( currentForm,
                                    nField );
    field = (FieldType*)FrmGetObjectPtr( currentForm,
                                         fieldIndex );
    hOldTextHandle = FldGetTextHandle( field );

    // Set the new text
    FldSetTextHandle( field,
                      hNewTextHandle );
    FldDrawField( field );


    if ( hOldTextHandle )
    {
        MemHandleFree( hOldTextHandle );
    }
}

/**
 * Updates a field on the current form with the given number.
 *
 * @param[in] nNumber Number to show in the field.
 * @param[in] nField The field ID to populate.
 */
static void PopulateFieldWithNumber( UInt16 nNumber,
                                     UInt16 nField )
{
    MemHandle   hOldTextHandle;
    MemHandle   hNewTextHandle;
    FormPtr     currentForm;
    Char*       pFieldString;

    FieldType* field;
    UInt16     fieldIndex;

    currentForm = FrmGetActiveForm();

    // Allocate a handle for our new field text
    hNewTextHandle = MemHandleNew( maxStrIToALen );
    pFieldString = (Char*)MemHandleLock( hNewTextHandle );

    // Format the number of records into a string
    StrIToA( pFieldString,
             nNumber );
    MemHandleUnlock( hNewTextHandle );

    // Deal with the field
    fieldIndex = FrmGetObjectIndex( currentForm,
                                    nField );
    field = (FieldType*)FrmGetObjectPtr( currentForm,
                                         fieldIndex );
    hOldTextHandle = FldGetTextHandle( field );

    // Set the new text
    FldSetTextHandle( field,
                      hNewTextHandle );
    FldDrawField( field );

    if ( hOldTextHandle )
    {
        MemHandleFree( hOldTextHandle );
    }
}

/**
 * Displays a modal error based on the error code given.
 *
 * @param[in] errorCode PalmOS error code to display.
 */
static void ShowVFS_ErrorAlert( Err errorCode )
{
    Char        bufErrorCodeString[ 10 ];
    const Char* pErrorString = "\0";

    switch ( errorCode )
    {
        case vfsErrFileBadRef:
        {
            pErrorString = "Internal file handling error";
            break;
        }
        case vfsErrVolumeBadRef:
        {
            pErrorString = "Memory card not found";
            break;
        }
        default:
        {
            pErrorString = "Unknown VFS error";
            break;
        }
    }

    sprintf( bufErrorCodeString,
             "0x%X",
             errorCode );

    FrmCustomAlert( AlertVFS_Error,
                    pErrorString,
                    bufErrorCodeString,
                    NULL );
}

/**
 * Initializes the MainForm form.
 *
 * @param[in] frmP Pointer to the MainForm form.
 */
static void MainFormInit( FormType* frmP )
{
    PopulateMainFormVersion();
}

/**
 * Initializes the ConnectedForm form.
 *
 * @param[in] frmP Pointer to the ConnectedForm form.
 */
static void ConnectedFormInit( FormType* frmP )
{
    if ( g_pMegasquirtInterface )
    {
        if ( g_pMegasquirtInterface->ReceivedSignature() )
        {
            PopulateFieldWithString( g_pMegasquirtInterface->GetSignature(),
                                     ConnectedFieldSignature );
        }
    }
}

/**
 * Initializes the LoggingForm form.
 *
 * @param[in] frmP Pointer to the LoggingForm form.
 */
static void LoggingFormInit( FormType* frmP )
{
    PopulateFieldWithNumber( 0,
                             LoggingFieldNumRecords );
    PopulateFieldWithString( g_pMegasquirtInterface->GetSignature(),
                             LoggingFieldSignature );
}

/**
 * Initializes the ManageLogsForm form.
 *
 * @param[in] frmP Pointer to the ManageLogsForm form.
 */
static void ManageLogsFormInit( FormType* frmP )
{
    FormPtr activeForm = NULL;
    ListPtr list       = NULL;
    UInt16  listIndex  = 0;

    activeForm    = FrmGetActiveForm();
    listIndex     = FrmGetObjectIndex( activeForm,
                                       ManageLogsDatabaseList );
    list          = (ListPtr)FrmGetObjectPtr( activeForm,
                                              listIndex );

    LstSetListChoices( list,
                       g_pLogManager->GetDatabaseNames(),
                       g_pLogManager->GetNumDatabaseNames() );
    LstDrawList( list );
}

/**
 * Initializes the LogInfoForm form.
 *
 * @param[in] frmP Pointer to the LogInfoForm form.
 */
static void LogInfoFormInit( FormType* frmP )
{
    PopulateFieldWithNumber( g_pSelectedLogDatabase->GetNumRecords(),
                             LogInfoFieldNumRecords );
    PopulateFieldWithString( g_pSelectedLogDatabase->GetName(),
                             LogInfoFieldName );
}

/**
 * Performs the menu command specified.
 *
 * @param[in] command The menu item ID.
 */
static Boolean MainFormDoCommand( UInt16 command )
{
    Boolean handled = false;

    switch (command)
    {
        case OptionsPreferences:
        {
            FormType*    frmP           = NULL;
            FieldType*   fieldRTU_Size  = NULL;
			FieldType*   fieldRTU_Delay = NULL;
			FieldType*   fieldRTU_Char  = NULL;
	        MemPtr       pText          = NULL;
            UInt16       controlID      = 0;
            MemHandle    handle         = 0;

            /* Clear the menu status from the display */
            MenuEraseStatus(0);

            /* Initialize the preference form. */
            frmP = FrmInitForm( PrefsForm );

            /*
             * Set the controls in the preference dialog to reflect our
             * preference data structure
             */
            fieldRTU_Size = (FieldType*)FrmGetObjectPtr( frmP,
                                                         FrmGetObjectIndex(frmP, PerfsFieldRTU_Size) );
            if ( fieldRTU_Size && (g_prefs.nRTU_Size != 0) )
            {
                Char   formattedNumber[ maxStrIToALen ];
                StrIToA( formattedNumber,
                         g_prefs.nRTU_Size );

                UInt32 len = StrLen( formattedNumber );

                handle = FldGetTextHandle( fieldRTU_Size );
                if ( !handle )
                {
                    handle = MemHandleNew( len + 1 );
                }
                else
                {
                    MemHandleResize( handle,
                                     len + 1 );
                }

                pText = MemHandleLock( handle );
                if ( pText )
                {
                    StrCopy( (Char*)pText,
                             formattedNumber );
                }

                MemHandleUnlock( handle );
                FldSetTextHandle( fieldRTU_Size,
                   				  handle );

                handle = 0;
            }
			
			fieldRTU_Delay = (FieldType*)FrmGetObjectPtr( frmP,
                                                          FrmGetObjectIndex(frmP, PerfsFieldRTU_Delay) );
            if ( fieldRTU_Delay )
            {
                Char   formattedNumber[ maxStrIToALen ];
                StrIToA( formattedNumber,
                         g_prefs.nRTU_Delay );

                UInt32 len = StrLen( formattedNumber );

                handle = FldGetTextHandle( fieldRTU_Delay );
                if ( !handle )
                {
                    handle = MemHandleNew( len + 1 );
                }
                else
                {
                    MemHandleResize( handle,
 					                 len + 1 );
                }

                pText = MemHandleLock( handle );
                if ( pText )
                {
                    StrCopy( (Char*)pText,
                             formattedNumber );
                }

                MemHandleUnlock( handle );
                FldSetTextHandle( fieldRTU_Delay,
 				                  handle );

                handle = 0;
            }

			fieldRTU_Char = (FieldType*)FrmGetObjectPtr(frmP, FrmGetObjectIndex(frmP, PerfsFieldRTU_Char));
            if ( fieldRTU_Char && (g_prefs.nRTU_Character != 0) )
            {
                handle = FldGetTextHandle( fieldRTU_Char );
                if ( !handle )
                {
                    handle = MemHandleNew( 2 );
                }
                else
                {
                    MemHandleResize( handle,
                                     2 );
                }

                pText = MemHandleLock( handle );
                if ( pText )
                {
				    ((Char*)pText)[0] = g_prefs.nRTU_Character;
				    ((Char*)pText)[1] = '\0';
                }

                MemHandleUnlock( handle );
                FldSetTextHandle( fieldRTU_Char,
 				                  handle );

                handle = 0;
            }			
			
            /*
             * Display the preferences dialog. The call will block until
             * the dialog is dismissed
             */
            controlID = FrmDoDialog (frmP);

            /* controlID contains the ID of the button used to dismiss the dialog */
            if ( controlID == PrefsOKButton )
            {
                /*
                 * The user hit the OK button. Get the value of the controls
                 * and store them in our pref struct
                 */
                if ( fieldRTU_Size )
                {
                    handle = FldGetTextHandle( fieldRTU_Size );
                    if ( handle )
                    {
                        pText = MemHandleLock( handle );
                        if ( pText )
                        {
                            /* Guard against the field text being longer than our pref's buffer */
                            g_prefs.nRTU_Size = StrAToI( (const Char*)pText );
                        }

                        MemHandleUnlock( handle );
                    }
                }
				
				if ( fieldRTU_Delay )
                {
                    handle = FldGetTextHandle( fieldRTU_Delay );
                    if ( handle )
                    {
                        pText = MemHandleLock( handle );
                        if ( pText )
                        {
                            /* Guard against the field text being longer than our pref's buffer */
                            g_prefs.nRTU_Delay = StrAToI( (const Char*)pText );
                        }

                        MemHandleUnlock( handle );
                    }
                }
				
				if ( fieldRTU_Char )
                {
                    handle = FldGetTextHandle( fieldRTU_Char );
                    if ( handle )
                    {
                        pText = MemHandleLock( handle );
                        if ( pText )
                        {
                            g_prefs.nRTU_Character = *(const Char*)pText;
                        }

                        MemHandleUnlock( handle );
                    }
                }
            }

            /* Clean up */
            FrmDeleteForm (frmP);

            handled = true;
            break;
        }

    }

    return handled;
}

/**
 * Brings up the global MegasquirtInterface instantiation.
 */
static void InitMegasquirtInterface( void )
{
    if ( !g_pMegasquirtInterface )
    {
        Err tSerialOpenError = errNone;

        tSerialOpenError = SrmOpen( serPortCradleRS232Port,
                                    115200,
                                    &g_nSerialPortID );

        g_pMegasquirtInterface = new MegasquirtInterface( g_nSerialPortID,
                                                          g_prefs.nRTU_Size,
														  g_prefs.nRTU_Delay,
                                                          g_prefs.nRTU_Character );
        g_pMegasquirtInterface->RequestSignature();
    }
}

/**
 * Cleans up the global MegasquirtInterface instantiation.
 */
static void DeinitMegasquirtInterface( void )
{
    if ( g_pMegasquirtInterface )
    {
        Err serialCloseError = errNone;

        delete g_pMegasquirtInterface;
        g_pMegasquirtInterface = NULL;

        serialCloseError = SrmClose( g_nSerialPortID );
        g_nSerialPortID = 0;
    }
}

/**
 * Event handler for the "MainForm" of the application.
 *
 * @param[in] eventP Pointer to an EventType structure.
 *
 * @return true if the event was handled and should not be passed to
 *           FrmHandleEvent.
 */
static Boolean MainFormHandleEvent( EventType* eventP )
{
    Boolean   handled = false;


    switch (eventP->eType)
    {
        case menuEvent:
        {
            handled = MainFormDoCommand( eventP->data.menu.itemID );
            break;
        }
        case frmOpenEvent:
        {
            FormType* frmP = FrmGetActiveForm();

            FrmDrawForm( frmP );
            MainFormInit( frmP );
            handled = true;
            break;
        }
        case frmUpdateEvent:
        {
            /*
             * To do any custom drawing here, first call
             * FrmDrawForm(), then do your drawing, and
             * then set handled to true.
             */
            break;
        }
        case ctlSelectEvent:
        {
            if      ( eventP->data.ctlSelect.controlID == MainConnectButton )
            {
                /* "Connect" button was hit */
                InitMegasquirtInterface();
                FrmGotoForm( ConnectedForm );
                handled = true;
            }
            else if ( eventP->data.ctlSelect.controlID == MainManageLogsButton )
            {
                /* The "Manage Logs" button was hit */
                if ( !g_pLogManager )
                {
                    g_pLogManager = new LogManager();
                }
                FrmGotoForm( ManageLogsForm );

                handled = true;
            }
            break;
        }
        case keyDownEvent:
        {
            if ( eventP->data.keyDown.chr == vchrPageUp  )
            {
                /* Up button was hit */
                InitMegasquirtInterface();
                FrmGotoForm( ConnectedForm );
                handled = true;
            }
            break;
        }
    }

    return handled;
}

/**
 * Starts logging data to a new database.
 */
static void StartLogging( void )
{
    if ( g_pMegasquirtInterface->ReceivedSignature() )
    {
        bool bLogFileOK = false;
        g_nNumUpdateRecords = 0;

        if ( !g_pLogDatabase )
        {
            UInt32       nTimeInSeconds = 0;
            DateTimeType nCurrentDateTime;

            nTimeInSeconds = TimGetSeconds();
            TimSecondsToDateTime( nTimeInSeconds,
                                  &nCurrentDateTime );

            g_pLogDatabase = new LogDatabase( nCurrentDateTime );
            if ( g_pLogDatabase->GetLastError() == errNone )
            {
                bLogFileOK = true;
                g_pLogDatabase->WriteHeader( nCurrentDateTime,
                                             g_pMegasquirtInterface->GetSignature(),
                                             g_prefs.nRTU_Size );
            }
            else
            {
                ShowVFS_ErrorAlert( g_pLogDatabase->GetLastError() );
            }
        }
        else
        {
            bLogFileOK = true;
        }

        if ( bLogFileOK )
        {
            // Disable auto shutoff
            g_nPreviousAutoOffTime = SysSetAutoOffTime( 0 );

            FrmGotoForm( LoggingForm );
            g_pMegasquirtInterface->RequestRealtimeUpdate();
        }
    }
    else
    {
        FrmAlert( NoSignatureAlert );
    }
}

/**
 * Stops a data logging session in progress.
 */
static void StopLogging( void )
{
    if ( g_pLogDatabase )
    {
        delete g_pLogDatabase;
        g_pLogDatabase = NULL;

        SysSetAutoOffTime( g_nPreviousAutoOffTime );
    }
}

/**
 * Event handler for the "ConnectedForm" of the application.
 *
 * @param[in] eventP Pointer to an EventType structure.
 *
 * @return true if the event was handled and should not be passed to
 *           FrmHandleEvent.
 */
static Boolean ConnectedFormHandleEvent( EventType* eventP )
{
    Boolean handled = false;
    FormType* frmP;

    switch ( eventP->eType )
    {
        case frmOpenEvent:
        {
            frmP = FrmGetActiveForm();
            FrmDrawForm(frmP);
            ConnectedFormInit(frmP);
            handled = true;
            break;
        }
        case frmUpdateEvent:
        {
            /*
             * To do any custom drawing here, first call
             * FrmDrawForm(), then do your drawing, and
             * then set handled to true.
             */
            break;
        }
        case ctlSelectEvent:
        {
            if      ( eventP->data.ctlSelect.controlID == ConnectedButtonStartLogging )
            {
                /* "Start Logging" button was hit */
                StartLogging();
                handled = true;
            }
            else if ( eventP->data.ctlSelect.controlID == ConnectedButtonDisconnect )
            {
                /* "Disconnect" button was hit */
                DeinitMegasquirtInterface();
                FrmGotoForm( MainForm );
                handled = true;
            }
            break;
        }
        case keyDownEvent:
        {
            if      ( eventP->data.keyDown.chr == vchrPageUp  )
            {
                StartLogging();
                handled = true;
            }
            else if ( eventP->data.keyDown.chr == vchrPageDown  )
            {
                /* Down button was hit */
                DeinitMegasquirtInterface();
                FrmGotoForm( MainForm );
                handled = true;
            }
            break;
        }
        case nilEvent:
        {
            if ( g_pMegasquirtInterface )
            {
                g_pMegasquirtInterface->HandleNilEvent();

                if ( g_pMegasquirtInterface->CheckSignatureEvent() )
                {
                    PopulateFieldWithString( g_pMegasquirtInterface->GetSignature(),
                                             ConnectedFieldSignature );
                }
            }
            break;
        }
    }

    return handled;
}

/**
 * Event handler for the "LoggingForm" of the application.
 *
 * @param[in] eventP Pointer to an EventType structure.
 *
 * @return true if the event was handled and should not be passed to
 *           FrmHandleEvent.
 */
static Boolean LoggingFormHandleEvent( EventType* eventP )
{
    Boolean handled = false;
    FormType* frmP;

    switch (eventP->eType)
    {
        case frmOpenEvent:
        {
            frmP = FrmGetActiveForm();
            FrmDrawForm(frmP);
            LoggingFormInit(frmP);
            handled = true;
            break;
        }

        case frmUpdateEvent:
        {
            /*
             * To do any custom drawing here, first call
             * FrmDrawForm(), then do your drawing, and
             * then set handled to true.
             */
            break;
        }

        case ctlSelectEvent:
        {
            if ( eventP->data.ctlSelect.controlID == LoggingButtonStop )
            {
                /* "Stop" button was hit */
                StopLogging();

                FrmGotoForm( ConnectedForm );
                handled = true;
            }

            break;
        }

        case keyDownEvent:
        {
            if ( eventP->data.keyDown.chr == vchrPageDown  )
            {
                /* Down button was hit */
                StopLogging();

                FrmGotoForm( ConnectedForm );
                handled = true;
            }
            break;
        }

        case nilEvent:
        {
            if ( g_pMegasquirtInterface )
            {
                g_pMegasquirtInterface->HandleNilEvent();

                if ( g_pMegasquirtInterface->CheckRealtimeUpdateEvent() )
                {
                    g_pLogDatabase->WriteOutputRecord( g_pMegasquirtInterface->GetRealtimeUpdateData()  );
                    if ( g_pLogDatabase->GetLastError() != errNone )
                    {
                        ShowVFS_ErrorAlert( g_pLogDatabase->GetLastError() );
                    }
                    g_nNumUpdateRecords++;
                    g_pMegasquirtInterface->RequestRealtimeUpdate();
                    PopulateFieldWithNumber( g_nNumUpdateRecords,
                                             LoggingFieldNumRecords );
                }
            }
            break;
        }
    }

    return handled;
}

/**
 * Event handler for the "ManageLogsForm" of the application.
 *
 * @param[in] eventP Pointer to an EventType structure.
 *
 * @return true if the event was handled and should not be passed to
 *           FrmHandleEvent.
 */
static Boolean ManageLogsFormHandleEvent( EventType* eventP )
{
    Boolean handled = false;
    FormType* frmP;

    switch (eventP->eType)
    {
        case frmOpenEvent:
        {
            frmP = FrmGetActiveForm();
            FrmDrawForm(frmP);
            ManageLogsFormInit(frmP);
            handled = true;
            break;
        }

        case frmUpdateEvent:
        {
            /*
             * To do any custom drawing here, first call
             * FrmDrawForm(), then do your drawing, and
             * then set handled to true.
             */
            break;
        }

        case lstSelectEvent:
        {
            if ( eventP->data.lstSelect.listID == ManageLogsDatabaseList )
            {
                Int16 nSelection   = eventP->data.lstSelect.selection;
                Char* databaseName = LstGetSelectionText( eventP->data.lstSelect.pList,
                                                          nSelection );

                if ( g_pSelectedLogDatabase )
                {
                    delete g_pSelectedLogDatabase;
                    g_pSelectedLogDatabase = NULL;
                }
                g_pSelectedLogDatabase = new SelectedLogDatabase( databaseName );

                /*
                 * Delete the LogManager AFTER bringing up the
                 *   SelectedLogDatabase as we need databaseName to still
                 *   exist.
                 */
                if ( g_pLogManager )
                {
                    delete g_pLogManager;
                    g_pLogManager = NULL;
                }

                if ( g_pSelectedLogDatabase->GetLastError() != errNone )
                {
                    // This should be changed to a database alert
                    ShowVFS_ErrorAlert( g_pLogDatabase->GetLastError() );
                }
                else
                {
                    FrmGotoForm( LogInfoForm );
                }

                handled = true;
            }
            break;
        }

        case ctlSelectEvent:
        {
            if ( eventP->data.ctlSelect.controlID == ManageLogsOkButton )
            {
                /* "Ok" button was hit */
                if ( g_pLogManager )
                {
                    delete g_pLogManager;
                    g_pLogManager = NULL;
                }

                FrmGotoForm( MainForm );
                handled = true;
            }

            break;
        }
    }

    return handled;
}

/**
 * Event handler for the "LogInfoForm" of the application.
 *
 * @param[in] eventP Pointer to an EventType structure.
 *
 * @return true if the event was handled and should not be passed to
 *           FrmHandleEvent.
 */
static Boolean LogInfoFormHandleEvent( EventType* eventP )
{
    Boolean handled = false;
    FormType* frmP;

    switch (eventP->eType)
    {
        case frmOpenEvent:
        {
            frmP = FrmGetActiveForm();
            FrmDrawForm(frmP);
            LogInfoFormInit(frmP);
            handled = true;
            break;
        }

        case frmUpdateEvent:
        {
            /*
             * To do any custom drawing here, first call
             * FrmDrawForm(), then do your drawing, and
             * then set handled to true.
             */
            break;
        }

        case ctlSelectEvent:
        {
            if      ( eventP->data.ctlSelect.controlID == LogInfoOkButton )
            {
                /* "Ok" button was hit */
                if ( g_pSelectedLogDatabase )
                {
                    delete g_pSelectedLogDatabase;
                    g_pSelectedLogDatabase = NULL;
                }

                if ( !g_pLogManager )
                {
                    g_pLogManager = new LogManager();
                }

                FrmGotoForm( ManageLogsForm );
                handled = true;
            }
            else if ( eventP->data.ctlSelect.controlID == LogInfoCopyButton )
            {
                /* "Copy to Card" button was hit */
                if ( g_pSelectedLogDatabase )
                {
                    if ( VFS_ManagerPresent() )
                    {
                        g_pSelectedLogDatabase->CopyToCard( g_prefs.nRTU_Size );
                        FrmAlert( AlertLogCopied );
                    }
                    else
                    {
                        FrmAlert( AlertNoVfsSupport );
                    }
                }
                handled = true;
            }
            else if ( eventP->data.ctlSelect.controlID == LogInfoDeleteButton )
            {
                /* "Delete" button was hit */
                g_pSelectedLogDatabase->Delete();
                if ( g_pSelectedLogDatabase )
                {
                    delete g_pSelectedLogDatabase;
                    g_pSelectedLogDatabase = NULL;
                }

                if ( !g_pLogManager )
                {
                    g_pLogManager = new LogManager();
                }

                FrmGotoForm( ManageLogsForm );
                handled = true;
            }

            break;
        }
    }

    return handled;
}

/**
 * Loads form resources and set the event handler for the form loaded.
 *
 * @param[in] eventP Pointer to an EventType structure.
 *
 * @return true if the event was handled and should not be passed to a higher
 *           level handler.
 */
static Boolean AppHandleEvent(EventType* eventP)
{
    UInt16 formId;
    FormType* frmP;

    if (eventP->eType == frmLoadEvent)
    {
        /* Load the form resource. */
        formId = eventP->data.frmLoad.formID;
        frmP = FrmInitForm(formId);
        FrmSetActiveForm(frmP);

        /*
         * Set the event handler for the form.  The handler of the
         * currently active form is called by FrmHandleEvent each
         * time is receives an event.
         */
        switch ( formId )
        {
            case MainForm:
            {
                FrmSetEventHandler( frmP,
                                    MainFormHandleEvent );
                break;
            }
            case ConnectedForm:
            {
                FrmSetEventHandler( frmP,
                                    ConnectedFormHandleEvent );
                break;
            }
            case LoggingForm:
            {
                FrmSetEventHandler( frmP,
                                    LoggingFormHandleEvent );
                break;
            }
            case ManageLogsForm:
            {
                FrmSetEventHandler( frmP,
                                    ManageLogsFormHandleEvent );
                break;
            }
            case LogInfoForm:
            {
                FrmSetEventHandler( frmP,
                                    LogInfoFormHandleEvent );
                break;
            }
        }
        return true;
    }

    return false;
}

/**
 * The main event loop for the application.
 */
static void AppEventLoop(void)
{
    UInt16 error;
    EventType event;

    do
    {
        /* change timeout if you need periodic nilEvents */
        EvtGetEvent(&event, evtWaitForever);

        if (! SysHandleEvent(&event))
        {
            if (! MenuHandleEvent(0, &event, &error))
            {
                if (! AppHandleEvent(&event))
                {
                    FrmDispatchEvent(&event);
                }
            }
        }
    }
    while (event.eType != appStopEvent);
}

/**
 * Get the current application's preferences.
 *
 * @return errNone - if nothing went wrong.
 */
static Err AppStart(void)
{
    UInt16 prefsSize;

    /* load the MathLib library, save reference in MathLibRef */
    {
        MathLibRef = sysInvalidRefNum;

        if (errNone != SysLibFind(MathLibName, &MathLibRef))
        {
            SysLibLoad(LibType, MathLibCreator, &MathLibRef);
        }

        if (MathLibRef != sysInvalidRefNum)
        {
            if (mlErrNone != MathLibOpen(MathLibRef, MathLibVersion))
            {
                /* if error loading library, reset reference */
                MathLibRef = sysInvalidRefNum;
            }
        }
    }
    /* Read the saved preferences / saved-state information. */
    prefsSize = sizeof(g_prefs);
    if (PrefGetAppPreferences(
                appFileCreator, appPrefID, &g_prefs, &prefsSize, true) ==
            noPreferenceFound)
    {
        /* no prefs; initialize pref struct with default values */
        g_prefs.nRTU_Size      = 169;
        g_prefs.nRTU_Delay     = 0;
        g_prefs.nRTU_Character = 'A';
    }
    else if ( g_prefs.nRTU_Character == 0 )
    {
        g_prefs.nRTU_Character = 'A';
    }

    return errNone;
}

/**
 * Saves the current state of the application.
 */
static void AppStop( void )
{
    /*
     * Write the saved preferences / saved-state information.  This
     * data will be saved during a HotSync backup.
     */
    PrefSetAppPreferences(
        appFileCreator, appPrefID, appPrefVersionNum,
        &g_prefs, sizeof(g_prefs), true);

    /* Close the Megasquirt Interface and serial port if needed */
    DeinitMegasquirtInterface();

    /* Close out the log file if needed */
    StopLogging();

    if ( g_pLogManager )
    {
        delete g_pLogManager;
        g_pLogManager = NULL;
    }

    if ( g_pSelectedLogDatabase )
    {
        delete g_pSelectedLogDatabase;
        g_pSelectedLogDatabase = NULL;
    }

    /* Close all the open forms. */
    FrmCloseAllForms();

    /* unload the MathLib library */
    if (MathLibRef != sysInvalidRefNum)
    {
        UInt16 useCount;

        MathLibClose(MathLibRef, &useCount);
        if (useCount == 0)
        {
            SysLibRemove(MathLibRef);
        }

        MathLibRef = sysInvalidRefNum;
    }

}

/**
 * Checks that a ROM version is meet your minimum requirement.
 *
 * @param[in] requiredVersion Minimum rom version required (see
 *                              sysFtrNumROMVersion in SystemMgr.h for format)
 * @param[in] launchFlags Flags that indicate if the application UI is
 *                          initialized.  These flags are one of the parameters
 *                          to your app's PilotMain.
 *
 * @return Error code or zero if ROM version is compatible.
 */
static Err RomVersionCompatible( UInt32 requiredVersion,
                                 UInt16 launchFlags )
{
    UInt32 romVersion;

    /* See if we're on in minimum required version of the ROM or later. */
    FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
    if (romVersion < requiredVersion)
    {
        if ((launchFlags &
                (sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp)) ==
                (sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp))
        {
            FrmAlert (RomIncompatibleAlert);

            /* Palm OS versions before 2.0 will continuously relaunch this
             * app unless we switch to another safe one. */
            if ( romVersion < kPalmOS20Version )
            {
                AppLaunchWithCommand( sysFileCDefaultApp,
                                      sysAppLaunchCmdNormalLaunch,
                                      NULL );
            }
        }

        return sysErrRomIncompatible;
    }

    return errNone;
}

/**
 * Main entry point for the application.
 *
 * @param[in] cmd Word value specifying the launch code.
 * @param[in] cmdPB Pointer to a structure that is associated with the launch
 *                    code.
 * @param[in] launchFlags Word value providing extra information about the
 *                          launch.
 *
 * @return Result of launch, errNone if all went OK.
 */
UInt32 PilotMain( UInt16 cmd,
                  MemPtr cmdPBP,
                  UInt16 launchFlags )
{
    Err error;

    error = RomVersionCompatible( ourMinVersion,
                                  launchFlags );
    if (error)
    {
        return (error);
    }

    switch (cmd)
    {
        case sysAppLaunchCmdNormalLaunch:
            error = AppStart();
            if (error)
            {
                return error;
            }

            /*
             * start application by opening the main form
             * and then entering the main event loop
             */
            FrmGotoForm(MainForm);
            AppEventLoop();

            AppStop();
            break;
    }

    return errNone;
}
